/**
 *******************************************************************************************************************************************
 * @file        userboot.h
 * @brief       IAP 实现 UserBoot 功能
 * @details     必看说明 :
 * 由于 UserBoot 属于一个功能模块, 需要结合 APP 使用, 且 UserBoot 并不是所有人都需要用, 故此处没有帮用户设置成 <开箱即用> 的方式.
 * 针对本工程内所有目标(除 UserBoot 自身) => 要使用 UserBoot ,
 * 必须在 Keil 下修改 每个 APP 固件 的 下载地址 与 大小, 将固件 下载起始地址 设为 [0x10000], 大小 设为 [0x70000].
 *
 * 修改方法须根据 在 Keil 工程的 Option -> Linker 的 [User Memory Layout from Target Dialog] 选项是否被<勾选> 分为如下情况:
 *
 * 一、[未勾选 X ] : 在 Keil 工程的 Option -> Linker 的 Scatter File 项点击 Edit... , 修改 [LR_IROM1] 与 [ER_IROM1] 的 <Start> <Size> 值
 *
 * 二、[已勾选 √ ] : 在 Keil 工程的 Option -> Target -> Read Only Memory Areas [on-chip] 中设置 <已被勾选的> Start 与 Size 值
 *
 * 如无特殊情况, 本例程已将所有目标(除 UserBoot 自身)设为上述第 一 种情形, 两种情形互斥, 一方有效则另一方无效.
 *
 * 而对于 UserBoot 目标, 笔者已将各项值设定好/无须用户修改, 此外, 上述所有值均依赖于本文件的 <UserBoot_Size = 0x10000> 计算得出.
 * @since       Change Logs:
 * Date         Author       Notes
 * 2022-12-08   lzh          the first version
 * @remark      UserBoot 编写说明 :  Device Firmware Update (OTA须借助外部无线模块)
 *
 * 加载方式 : 待更新的固件以文件形式存储于用户外部存储介质中, 待用户插入外部存储介质,
 * 将待更新的固件拷贝至 SPI-Flash 本地离线存储, 便于当用户选择合适的时机再(重启)执行更新动作 拷贝至 Chip-Flash 执行
 * 如此使得 固件下载拷贝 与 固件更新 的动作得到分隔, 当用户执行 固件更新 时, 如检测到 Chip-Flash 中已有固件,
 * 会将 旧固件 备份至 SPI-Flash 本地离线存储, 用于系统宕机时的 固件恢复, 若更新成功则会再次备份现有最新固件.
 *
 * 存储方式 : 双分区[备份区放在 SPI-Flash ] <若备份区放在 Chip-Flash 对 Chip-Flash 大小要求较高, 需要压缩 APP>,
 * 后台式下载新固件至 SPI-Flash , 在更新至 chip-Flash 时作读出 旧固件 备份至 SPI-Flash,
 * 若固件更新成功, 则保留在 SPI-Flash 本地离线存储 的待更新固件 会删除掉, 避免触发重复更新动作.
 *
 * 加密方式 : 固件(*.bin)头部加入固件信息头(带 CRC32 校验),
 * 利用每一颗芯片内的 UID 作 <密钥> 将存储在 SPI-Flash 本地的离线固件进行加密,
 * 除本机外其他机器无法使用, 但不利于 SPI-Flash 母片批量烧录方法
 *
 * (SWM341 没有硬件加密模块, 须软件实现加密算法: 对称 - AES, 非对称 - RSA/ECC, 哈希 - MD5<不可还原>)
 *
 * 须提示用户, 在更新时出现1~2次重启是正常现象, 例如 Windows 更新.
 *
 * 由于本例程适配各种分辨率的屏, 加之考虑到更新过程不会经常触发, 本着在 UB 中尽量精简 外设/功能 的设计原则, 故目前的 UserBoot 未实现屏显.
 * 更新过程中 可以增加不同颜色 LED 灯 快/慢 闪烁、蜂鸣器发声 等用户提示.
 *
 * @test        实际测试 UserBoot 更新 APP 固件时间约 30 s 以内, 备份 APP 同上.
 * @todo        增加线性地址写入
 *******************************************************************************************************************************************
 * @attention
 *
 * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS WITH CODING INFORMATION
 * REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME. AS A RESULT, SYNWIT SHALL NOT BE HELD LIABLE
 * FOR ANY DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
 * OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION CONTAINED HEREIN IN CONN-
 * -ECTION WITH THEIR PRODUCTS.
 *
 * @copyright   2012 Synwit Technology
 *******************************************************************************************************************************************
 */
#ifndef __USERBOOT_H__
#define __USERBOOT_H__

#include "SWM341.h"
#include "load_file.h"

//=================================================宏定义========================================================//
/* UserBoot 引导启动标志(uint32_t) 枚举 -- 魔数 */
#define GO_APP              (0x12345678) /**< 运行固件 */
#define EXEC_UPDATE         (0x23456789) /**< 更新固件(检测到有可更新的固件) */
#define BACKUP_RESTORE      (0x3456789A) /**< 恢复固件(从备份中) */

//================================================类型声明=======================================================//
/** 数据加密/解密回调 */
typedef int (*data_code_cb)(const void *src_buff, void *dst_buff, uint32_t bytes);

//================================================变量声明=======================================================//
/** 存放至本地 (SPI-Flash) 的待更新固件文件名 */
extern const char *const Firmware_Name_Update;
/** 存放至本地 (SPI-Flash) 的备份固件文件名 */
extern const char *const Firmware_Name_Backup;
/** 本地磁盘类型 */
extern const disk_t Firmware_Disk;

//================================================函数声明=======================================================//
/**
 * @brief   UserBoot 功能
 * @param   /
 * @retval  /
 * @note    本函数会一直阻塞在被调用处, 之后的语句都不会得到执行
 */
void userboot(void);

/**
 * @brief   跳转至 UserBoot 执行对应动作
 * @param   boot_flag : UserBoot 引导启动标志(详见对应枚举)
 * @param   wait      : 是否立即跳转 (true-立即复位跳转      false-等待用户手动复位后跳转)
 * @retval  /
 * @note    此函数会改变 Chip-Flash 内部分区域的数据, 若片内不含 UB 程序, 则调用本程序会导致 APP 的正常数据被破坏.
 */
void jump_to_userboot(uint32_t boot_flag, bool wait);

/**
 * @brief   固件下载 (外部 => 本地)
 * @param   src_path : 外部固件绝对路径
 * @param   dst_path : 本地固件绝对路径
 * @param   data_enc : 数据加密回调
 * @retval  0        : success
 * @retval  other    : error code
 * @pre     须保证 源/目标 固件对应磁盘的文件系统处于挂载状态
 */
uint8_t firmware_download(const char *src_path, const char *dst_path, data_code_cb data_enc);

/**
 * @brief   数据加密
 * @param   src_buff : 数据源
 * @param   dst_buff : 输出流
 * @param   bytes    : 数据量( AES 分组长度 128 bit == 16 Bytes, 数据量必须被该长度整除)
 * @retval  0        : success
 * @retval  other    : error code
 * @note    可使用 芯片唯一 96 bit ID 实现加密算法: 对称 - AES, 非对称 - RSA/ECC, 哈希 - MD5<不可还原>
 */
int data_encode(const void *src_buff, void *dst_buff, uint32_t bytes);

/**
 * @brief   数据解密
 * @param   src_buff : 数据源
 * @param   dst_buff : 输出流
 * @param   bytes    : 数据量( AES 分组长度 128 bit == 16 Bytes, 数据量必须被该长度整除)
 * @retval  0        : success
 * @retval  other    : error code
 * @note    依据 数据加密规则 data_encode() 逆向进行解码
 */
int data_decode(const void *src_buff, void *dst_buff, uint32_t bytes);

#endif //__USERBOOT_H__
